#include <paging.h>
#include "system.h"
#include <heap.h>

int *frames;
int nframes;

// The kernel's page directory
page_directory_t *kernel_directory=0;

// The current page directory;
page_directory_t *current_directory=0;

extern int placement_address;

#define INDEX_FROM_BIT(a) (a/(8*4))
#define OFFSET_FROM_BIT(a) (a%(8*4))

//Static function to set a bit in the frames bitset
static void set_frame(int frame_addr)
{
	int frame = frame_addr/0x1000;
	int idx=INDEX_FROM_BIT(frame);
	int off=OFFSET_FROM_BIT(frame);
	frames[idx] |= (0x1 << off);
}

//Static function to clear a bit in the frames bitset
static void clear_frame(int frame_addr)
{
	int frame=frame_addr/0x1000;
	int idx=INDEX_FROM_BIT(frame);
	int off=OFFSET_FROM_BIT(frame);
	frames[idx] &= ~(0x1 << off);
}

//Static function to test if a bit is set
static int test_frame(int frame_addr)
{
	int frame=frame_addr/0x1000;
	int idx=INDEX_FROM_BIT(frame);
	int off=OFFSET_FROM_BIT(frame);
	return (frames[idx]&(0x1<<off));
}

//static function to find the first free frame
static int first_frame()
{
	int i,j;
	for (i=0;i < INDEX_FROM_BIT(nframes); i++)
	{
		if (frames!=0xFFFFFFFF)
		{
			for (j=0;j<32;j++)
			{
				int  toTest = 0x1 << j;
				if (!(frames[i]&toTest))
				{
					return i*4*8+j;
				}
			}
		}
	}
}

void alloc_frame(page_t *page, int is_kernel, int is_writeable)
{
	if (page->frame!=0)
	{
		return;
	}
	else 
	{
		int idx =  first_frame();
		if (idx == (int)-1)
		{
			PANIC("No free frames\0");
		}
	set_frame(idx*0x1000);
	page->present=1;
	page->rw=(is_writeable)?1:0;
	page->user=(is_kernel)?0:1;
	page->frame=idx;
	}
}

void free_frame(page_t *page)
{
	int frame;
	if (!(frame=page->frame))
	{
		return;
	}
	else
	{
		clear_frame(frame);
		page->frame=0x0;
	}
}

void initialise_paging()
{
	
	int mem_end_page=0x1000000;
	nframes = mem_end_page / 0x1000;
	frames = (int *)kmalloc(INDEX_FROM_BIT(nframes));
	memset(frames,0,INDEX_FROM_BIT(nframes));
	
	//Construct page directory
	kernel_directory = (page_directory_t*)kmalloc_a(sizeof(page_directory_t));
	memset(kernel_directory,0,sizeof(page_directory_t));
	current_directory=kernel_directory;
	
	//The memory occupied by the kernel needs to be marked
	int i=0;
	while (i < placement_address)
	{
		alloc_frame (get_page(i,1,kernel_directory),0,0);
		i+= 0x1000;
		//printf("Alloc:%d\n\0",i);
	}
	
	//idt_set_gate(14, (unsigned)page_fault, 0x08, 0x8E);
	switch_page_directory(kernel_directory);
	
}

void switch_page_directory(page_directory_t *dir)
{
	current_directory = dir;
    	asm volatile("mov %0, %%cr3":: "r"(&dir->tablesPhysical));
    	int cr0;
    	asm volatile("mov %%cr0, %0": "=r"(cr0));
    	cr0 |= 0x80000000; // Enable paging!
    	asm volatile("mov %0, %%cr0":: "r"(cr0));
}

page_t *get_page(int address, int make, page_directory_t *dir)
{
	address /=0x1000;
	int table_idx = address / 1024;
	if (dir->tables[table_idx])
	{
		return &dir->tables[table_idx]->pages[address%1024];
	}
	else if(make)
	{
		int tmp;
		dir->tables[table_idx]=(page_table_t*)kmalloc_ap(sizeof(page_table_t),&tmp);
		memset(dir->tables[table_idx],0,0x1000);
		dir->tablesPhysical[table_idx] = tmp|0x7;
		return &dir->tables[table_idx]->pages[address%1024];
	}
	else
	{
		return 0;
	}
}

void page_fault(struct regs *r)
{
   // A page fault has occurred.
   // The faulting address is stored in the CR2 register.
   int faulting_address;
   asm volatile("mov %%cr2, %0" : "=r" (faulting_address));

   // The error code gives us details of what happened.
   int present   = !(r->err_code & 0x1); // Page not present
   int rw = r->err_code & 0x2;           // Write operation?
   int us = r->err_code & 0x4;           // Processor was in user-mode?
   int reserved = r->err_code & 0x8;     // Overwritten CPU-reserved bits of page entry?
   int id = r->err_code & 0x10;          // Caused by an instruction fetch?

   // Output an error message.
   printf("Page fault! ( \0");
   if (present) {printf("present \0");}
   if (rw) {printf("read-only \0");}
   if (us) {printf("user-mode \0");}
   if (reserved) {printf("reserved \0");}
   printf(") at 0x%x\n\0",faulting_address);
   PANIC("Page fault\n\0");
} 


